package cn.anecansaitin.whimsy.common.item.handcannon;

import cn.anecansaitin.whimsy.client.item.render.handcannon.HandCannonRender;
import cn.anecansaitin.whimsy.client.registers.ModSounds;
import cn.anecansaitin.whimsy.common.attachment.HandCannonMagazine;
import cn.anecansaitin.whimsy.common.attachment.ItemStackData;
import cn.anecansaitin.whimsy.common.entity.LightBullet;
import cn.anecansaitin.whimsy.common.register.ModDataAttachments;
import cn.anecansaitin.whimsy.util.HitResultUtil;
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.SingletonGeoAnimatable;
import software.bernie.geckolib.core.animatable.GeoAnimatable;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.core.animation.AnimationController;
import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.core.object.PlayState;
import software.bernie.geckolib.util.GeckoLibUtil;

import java.util.function.Consumer;

public class HandCannon extends Item implements GeoItem {
    public HandCannon() {
        super(new Properties().stacksTo(1));
        SingletonGeoAnimatable.registerSyncedAnimatable(this);
    }

    @Override
    public boolean onEntitySwing(ItemStack stack, LivingEntity entity) {
        return true;
    }

    @Override
    public boolean onLeftClickEntity(ItemStack stack, Player player, Entity entity) {
        return true;
    }

    @Override
    public boolean canAttackBlock(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer) {
        return false;
    }

    @Override
    public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) {
        return false;
    }

    @Override
    public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
        if (!(entity instanceof Player player)) {
            return;
        }

        HandCannonMagazine magazine = stack.getData(ModDataAttachments.HAND_CANNON_MAGAZINE);

        if (level.isClientSide) {
            if (magazine.shouldPlayReloadSound()) {
                player.playSound(ModSounds.HAND_CANNON_RELOAD.get());
            }

            return;
        }

        int recharge = magazine.tick();
        int auxiliaryAttackDelay = magazine.auxiliaryAttackDelay;

        if (recharge > 0) {
            triggerCannonAnim(player, stack, "switch", "charged" + (5 - recharge));
            //辅助攻击的前10tick动画无法被打断
            if (auxiliaryAttackDelay > 10) {
                return;
            }

            if (recharge < 4) {
                triggerWholeAnim(player, stack, "main", "charging");
            } else {
                triggerWholeAnim(player, stack, "main", "idle");
            }
        }

        if (auxiliaryAttackDelay == 10) {
            triggerWholeAnim(player, stack, "main", "auxiliary.back");
        }
    }

    public void auxiliaryAttack(LivingEntity attacker, ItemStack cannon) {
        HandCannonMagazine magazine = cannon.getData(ModDataAttachments.HAND_CANNON_MAGAZINE);

        if (magazine.auxiliaryAttack()) {
            DamageSource damageSource;

            if (attacker instanceof Player player) {
                damageSource = attacker.damageSources().playerAttack(player);
            } else {
                damageSource = attacker.damageSources().generic();
            }

            triggerWholeAnim(attacker, cannon, "main", "auxiliary");

            for (Entity entity : HitResultUtil.fanShapedRangeEntity(attacker, 2, 80, 10)) {
                entity.invulnerableTime = 9;
                entity.hurt(damageSource, 3f);
            }
        }
    }

    public void mainAttack(LivingEntity attacker, ItemStack cannon) {
        HandCannonMagazine magazine = cannon.getData(ModDataAttachments.HAND_CANNON_MAGAZINE);
        int shoot = magazine.shoot();

        if (shoot > 0) {
            attacker.level().playSound(null, attacker.getX(), attacker.getY(), attacker.getZ(), ModSounds.HAND_CANNON_SHOOT.get(), SoundSource.PLAYERS);
            triggerWholeAnim(attacker, cannon, "main", "shooting");
            triggerCannonAnim(attacker, cannon, "switch", "shoot" + (5 - shoot));
            float velocity = 3f;
            shootBullet(attacker, velocity, 0, 0);
            shootBullet(attacker, velocity, 2, 0);
            shootBullet(attacker, velocity, -2, 0);
            shootBullet(attacker, velocity, 0, 2);
            shootBullet(attacker, velocity, 0, -2);
            shootBullet(attacker, velocity, 1, 1);
            shootBullet(attacker, velocity, 1, -1);
            shootBullet(attacker, velocity, -1, 1);
            shootBullet(attacker, velocity, -1, -1);
        }
    }

    private void shootBullet(LivingEntity shooter, float velocity, float xAngle, float yAngle) {
        LightBullet middle = new LightBullet(shooter, null);

        if (yAngle == 0) {
            middle.shootFromRotation(shooter, shooter.getXRot() + xAngle, shooter.getYRot(), 0, velocity, 0);
        } else {
            Vec3 vec3 = shooter.getUpVector(1.0F);
            Quaternionf quaternionf = new Quaternionf().setAngleAxis(yAngle * (float) (Math.PI / 180.0), vec3.x, vec3.y, vec3.z);

            float f = (shooter.getXRot() + xAngle) * (float) (Math.PI / 180.0);
            float f1 = -shooter.getYRot() * (float) (Math.PI / 180.0);
            float f2 = Mth.cos(f1);
            float f3 = Mth.sin(f1);
            float f4 = Mth.cos(f);
            float f5 = Mth.sin(f);
            Vector3f view = new Vector3f(f3 * f4, -f5, f2 * f4);

            Vector3f vector3f = view.rotate(quaternionf);
            middle.shoot(vector3f.x(), vector3f.y(), vector3f.z(), velocity, 0);
        }

        shooter.level().addFreshEntity(middle);
    }

    @Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
        controllers.add(new AnimationController<GeoAnimatable>(this, "main", 0, (i) -> {
            AnimationController<GeoAnimatable> controller = i.getController();

            if (controller.getAnimationState() == AnimationController.State.STOPPED) {
                return PlayState.STOP;
            }

            return PlayState.CONTINUE;
        })
                .triggerableAnim("idle", IDLE)
                .triggerableAnim("shoot", SHOOT)
                .triggerableAnim("shooting", SHOOTING)
                .triggerableAnim("charging", CHARGING)
                .triggerableAnim("auxiliary", AUXILIARY)
                .triggerableAnim("auxiliary.back", AUXILIARY_BACK));

        controllers.add(new AnimationController<GeoAnimatable>(this, "switch", 0, (i) -> {
            if (i.getController().getAnimationState() == AnimationController.State.STOPPED) {
                return PlayState.STOP;
            }

            return PlayState.CONTINUE;
        })
                .triggerableAnim("shoot1", SWITCH_SHOOT1)
                .triggerableAnim("shoot2", SWITCH_SHOOT2)
                .triggerableAnim("shoot3", SWITCH_SHOOT3)
                .triggerableAnim("shoot4", SWITCH_SHOOT4)
                .triggerableAnim("charged1", SWITCH_CHARGED1)
                .triggerableAnim("charged2", SWITCH_CHARGED2)
                .triggerableAnim("charged3", SWITCH_CHARGED3)
                .triggerableAnim("charged4", SWITCH_CHARGED4));
    }

    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return geoCache;
    }

    @Override
    public void initializeClient(Consumer<IClientItemExtensions> consumer) {
        consumer.accept(new IClientItemExtensions() {
            @Nullable
            private HandCannonRender render;

            @Override
            public BlockEntityWithoutLevelRenderer getCustomRenderer() {
                if (render == null) {
                    render = new HandCannonRender();
                }

                return render;
            }
        });
    }

    public void triggerWholeAnim(LivingEntity user, ItemStack cannon, String controller, String anim) {
        long cannonId = GeoItem.getOrAssignId(cannon, (ServerLevel) user.level());
        triggerAnim(user, cannonId, controller, anim);
        geoCache.getManagerForId(cannonId).getAnimationControllers().get(controller).forceAnimationReset();

        ItemStackData data = cannon.getData(ModDataAttachments.HAND_CANNON_ARM);
        ItemStack arm = data.itemStack;
        long armId = GeoItem.getOrAssignId(arm, (ServerLevel) user.level());
        HandCannonArm armItem = (HandCannonArm) arm.getItem();
        armItem.triggerAnim(user, armId, controller, anim);
        armItem.getAnimatableInstanceCache().getManagerForId(armId).getAnimationControllers().get(controller).forceAnimationReset();
    }

    public void triggerCannonAnim(LivingEntity user, ItemStack cannon, String controller, String anim) {
        long cannonId = GeoItem.getOrAssignId(cannon, (ServerLevel) user.level());
        triggerAnim(user, cannonId, controller, anim);
//        geoCache.getManagerForId(cannonId).getAnimationControllers().get(controller).forceAnimationReset();
    }

    private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
    private static final RawAnimation IDLE = RawAnimation.begin()
            .thenPlay("idle");
    private static final RawAnimation SHOOT = RawAnimation.begin()
            .thenPlayAndHold("shoot");
    private static final RawAnimation SHOOTING = RawAnimation.begin()
            .thenPlayAndHold("shooting");
    private static final RawAnimation CHARGING = RawAnimation.begin()
            .thenPlayAndHold("charging");
    private static final RawAnimation AUXILIARY = RawAnimation.begin()
            .thenPlayAndHold("auxiliary");
    private static final RawAnimation AUXILIARY_BACK = RawAnimation.begin()
            .thenPlayAndHold("auxiliary.back");
    private static final RawAnimation SWITCH_SHOOT1 = RawAnimation.begin()
            .thenPlayAndHold("switch.shoot1");
    private static final RawAnimation SWITCH_SHOOT2 = RawAnimation.begin()
            .thenPlayAndHold("switch.shoot2");
    private static final RawAnimation SWITCH_SHOOT3 = RawAnimation.begin()
            .thenPlayAndHold("switch.shoot3");
    private static final RawAnimation SWITCH_SHOOT4 = RawAnimation.begin()
            .thenPlayAndHold("switch.shoot4");
    private static final RawAnimation SWITCH_CHARGED1 = RawAnimation.begin()
            .thenPlayAndHold("switch.charged1");
    private static final RawAnimation SWITCH_CHARGED2 = RawAnimation.begin()
            .thenPlayAndHold("switch.charged2");
    private static final RawAnimation SWITCH_CHARGED3 = RawAnimation.begin()
            .thenPlayAndHold("switch.charged3");
    private static final RawAnimation SWITCH_CHARGED4 = RawAnimation.begin()
            .thenPlayAndHold("switch.charged4");
}
